﻿// description  : 用户访问处理接口
// author       : tristan
// created      : 2011/3/14
// contact      : snaprack@gmail.com

namespace RBAC
{
    using System.IO;
    using System.Linq;
    using System.Web;
    using RBAC.Config;

    /// <summary>
    /// 用户访问处理接口, 置于真正的访问控制接口之前，适应不同的客户端请求，如Http或桌面程序。
    /// </summary>
    public interface IAccessHandler
    {
        /// <summary>
        /// 设置访问上下文
        /// </summary>
        void SetContext(object access);
        /// <summary>
        /// 验证用户访问是否有效
        /// </summary>
        bool Validate();
        /// <summary>
        /// 获得用户
        /// </summary>
        string ParseUser();
        /// <summary>
        /// 获得用户访问的资源编码
        /// </summary>
        string ParseResource();
        /// <summary>
        /// 获得访问控制接口实例
        /// </summary>
        IAccessControl GetAccessControl();
    }

    /// <summary>
    /// 用户访问接口实例化工厂 
    /// </summary>
    public static class AccessHandlerFactory
    {
        /// <summary>
        /// 获得访问处理接口实例, 并设置上下文。
        /// </summary>
        public static IAccessHandler GetHandler(object access)
        {
            if (access is HttpApplication)
            {
                IAccessHandler handler = new MultiSystemHttpAccessHandler();
                handler.SetContext(access);

                return handler;
            }

            throw new AccessControlException("无法处理的请求!");
        }
    }

    /// <summary>
    /// 通过Http访问处理
    /// </summary>
    public class HttpAccessHandler : IAccessHandler
    {
        protected HttpApplication _application;

        public virtual void SetContext(object access)
        {
            _application = access as HttpApplication;
        }

        public virtual bool TryGet(out string user, out string resource)
        {
            user = ParseUser();
            resource = ParseResource();
            
            return !string.IsNullOrEmpty(user) 
                && !string.IsNullOrEmpty(resource);
        }

        public virtual string ParseUser()
        {
            return _application.User == null 
                ? string.Empty
                : _application.User.Identity.Name;
        }

        public virtual string ParseResource()
        {
            return _application.Request.Url.PathAndQuery;
        }

        public virtual bool Validate()
        {
            return AccessControlConfigure.IsTurnedOn
                && File.Exists(_application.Request.PhysicalPath) 
                && !IsStatic((_application.Request.PhysicalPath))
                && !IsIgnore(_application.Request.Path);
        }

        public virtual IAccessControl GetAccessControl()
        {
            string user ,resource;

            if (TryGet(out user, out resource))
            {
                IAccessControl ctrl = new GeneralAccessControl();
                ctrl.SetContext(user, resource);

                return ctrl;
            }

            throw new AccessControlException("请求上下文不明确。");
        }
        /// <summary>
        /// 访问路径是否该忽略。可以在web.config中添加配置忽略项。
        /// </summary>
        /// <param name="path">访问路径</param>
        protected virtual bool IsIgnore(string path)
        {
            return AccessControlConfigure.IgnoreList.Contains(path.ToLower());
        }
        /// <summary>
        /// 访问目标属于静态内容
        /// </summary>
        /// <param name="ext">扩展名</param>
        protected virtual bool IsStatic(string ext)
        {
            string[] statics = { ".css", ".js", ".png", ".jpg", ".jpeg", ".gif", ".bmp" };

            return statics.Contains(ext.ToLower());
        }
    }

    /// <summary>
    /// 涉及多系统的用户访问处理实现。解决多系统共同使用rbac资源编码的问题，它用前缀标识区别于其他系统的资源编码。
    /// 资源编码示例：testSystem/default.aspx，testSystem来自于web.config/rbac[system]节点属性
    /// </summary>
    public class MultiSystemHttpAccessHandler : HttpAccessHandler
    {
        public override string ParseResource()
        {
            string path = base.ParseResource();
            string sys = RBAC.Config.AccessControlConfigure.SystemName;

            return string.Concat(sys, path);
        }
    }

}
